package saas.dev.cloud.config;

import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.StringReader;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Sql拦截器 自定义方法 自动追加逻辑删除
 *
 * @author : y1
 * @className : SqlModifyInterceptor
 * @date: 2022/8/4 16:29
 * @description : sql拦截器 自定义方法 自动追加逻辑删除
 */
@Slf4j
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class SqlModifyInterceptor implements Interceptor {
    /**
     * 删除字段
     */
    private static final String REMOVE_NAME = "is_removed";
    /**
     * 删除字段标识为0
     */
    private static final String REMOVE_NAME_ZORE = "is_removed = 0";
    /**
     * 未删除状态
     */
    private static final Integer REMOVE_TRUE = 0;
    /**
     * 过滤Sql
     */
    private final List<String> methodPackage=
            Stream.of("com.ever.business.andon.dao.AndonEventDao.getAndonEventNo")
            .collect(Collectors.toList());
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //通过MetaObject优雅访问对象的属性，这里是访问statementHandler的属性;：MetaObject是Mybatis提供的一个用于方便、
        //优雅访问对象属性的对象，通过它可以简化代码、不需要try/catch各种reflect异常，同时它支持对JavaBean、Collection、Map三种类型对象的操作。
        MetaObject metaObject = MetaObject
                .forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                        new DefaultReflectorFactory());
        //先拦截到RoutingStatementHandler，里面有个StatementHandler类型的delegate变量，其实现类是BaseStatementHandler，然后就到BaseStatementHandler的成员变量mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        //id为执行的mapper方法的全路径名，如saas.dev.cloud.xxx
        String id = mappedStatement.getId();
        if (methodPackage.contains(id)) {
            return invocation.proceed();
        }
//        log.info("id为执行的mapper方法的全路径名:" + id);
        //sql语句类型 select、delete、insert、update
        String sqlCommandType = mappedStatement.getSqlCommandType().toString();
//        log.info("sql语句类型:" + sqlCommandType);
        BoundSql boundSql = statementHandler.getBoundSql();
        /**
         这些代码是我在网上复制别人的，然后进行了一些小的修改，
         其中log.info()是我为了监控sql的运行情况而添加的代码，不需要可以删除，
         上面部分我不知道是不是必须的，所以我也没有删，下面是我主要修改的地方
         **/
        //获取到原始sql语句
        String sql = boundSql.getSql();
        if (sql.contains(REMOVE_NAME_ZORE)) {
            return invocation.proceed();
        }
        log.info("拦截的sql语句:" + sql);
        //可以先打印出原始的sql语句，然后根据实际情况修改，我个人建议是下面这样修改
        //只在原sql上添加条件而不是删除条件
        sql = makeSql(sql);
        log.info("修改之后的sql：" + sql);
        //通过反射修改sql语句
        //下面类似于替换sql
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, sql);
        return invocation.proceed();
    }

    /**
     * 自动追加removed=0
     *
     * @param sql
     * @return
     * @throws JSQLParserException
     */
    private String makeSql(String sql) throws JSQLParserException {
        CCJSqlParserManager parserManager = new CCJSqlParserManager();
        Statement parse = parserManager.parse(new StringReader(sql));
        if (parse instanceof Select) {
            Select select = (Select) parse;
            PlainSelect plain = (PlainSelect) select.getSelectBody();
            FromItem fromItem = plain.getFromItem();
            // desc 有别名用别名，无别名用表名，防止字段冲突报错
            String mainTableName = fromItem.getAlias() == null ? ((PlainSelect) select.getSelectBody()).getFromItem().toString() : fromItem.getAlias().getName();
            //构建子查询
            String dataAuthSql = mainTableName + "." + REMOVE_NAME + " = " + REMOVE_TRUE;
            if (plain.getWhere() == null) {
                plain.setWhere(CCJSqlParserUtil.parseCondExpression(dataAuthSql));
            } else {
                plain.setWhere(new AndExpression(plain.getWhere(), CCJSqlParserUtil.parseCondExpression(dataAuthSql)));
            }
            return select.toString();
        }
        return sql;
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
